package com.zyq.util;

import cn.hutool.json.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Redis 工具类
 * @author xiaochi
 * @date 2021/12/11 22:13
 */
@Component
public class RedisUtil {

    public static RedisUtil instance;

    private static final String KVS_KEY_SPLIT = ":";

    @Autowired
    private Environment environment;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private String applicationName;
    private String profileActive;

    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }

    public void setProfileActive(String profileActive) {
        this.profileActive = profileActive;
    }

    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        instance.stringRedisTemplate = stringRedisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        instance.redisTemplate = redisTemplate;
    }

    public StringRedisTemplate getStringRedisTemplate(){
        return instance.stringRedisTemplate;
    }

    public RedisTemplate<String, Object> getRedisTemplate() {
        return instance.redisTemplate;
    }

    @PostConstruct
    public void init() {
        instance = new RedisUtil();
        instance.setStringRedisTemplate(stringRedisTemplate);
        instance.setRedisTemplate(redisTemplate);
        instance.setApplicationName(environment.getProperty("spring.application.name"));
        instance.setProfileActive(environment.getProperty("spring.profiles.active"));
    }

    /**
     * stringRedisTemplate.opsForValue();　//操作字符串
     * stringRedisTemplate.opsForHash();　 //操作hash
     * stringRedisTemplate.opsForList();　 //操作list
     * stringRedisTemplate.opsForSet();　　//操作set
     * stringRedisTemplate.opsForZSet();　 //操作有序set
     */

    /**
     * assembleCacheKey所有键的拼接
     * @param key
     * @return
     */
    private static String assembleCacheKey(String key) {
        if (null == key || "".equals(key)){
            return null;
        }
        return instance.applicationName + instance.KVS_KEY_SPLIT + instance.profileActive + instance.KVS_KEY_SPLIT + key;
    }

    /**
     * multiGet批量查询（只能是str）
     * @param keys
     * @return List<String>
     */
    public static List<String> getmultiet(List<String> keys){
        if (CollectionUtils.isEmpty(keys)){
            return null;
        }
        keys = keys.stream().map(RedisUtil::assembleCacheKey).collect(Collectors.toList());
        return instance.stringRedisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 通过键来获取值
     * @param key
     * @return String
     */
    public static String get(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        return instance.stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 设置值
     * @param key
     * @param value
     */
    public static void set(String key,String value){
        set(key,value,null);
    }

    /**
     * 设置值
     * @param key
     * @param value
     * @param expired
     */
    public static void set(String key,String value,Long expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){
            return;
        }
        instance.stringRedisTemplate.opsForValue().set(key,value);
        if (null != expired){
            instance.stringRedisTemplate.expire(key,expired, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取值装成Int类型
     * @param key
     * @return
     */
    public static Integer getInt(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        String ret = instance.stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(ret)){
            return null;
        }
        try{
            return Integer.valueOf(ret);
        }catch (NumberFormatException e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除一个键
     * @param key
     * @return
     */
    public static boolean del(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return false;
        }
        try{
            Boolean ret = instance.stringRedisTemplate.delete(key);
            return null != ret && ret;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除一个键
     * @param key
     * @return
     */
    public static boolean delslike(String key){
        key = assembleCacheKey(key);
        Set<String> keys = instance.stringRedisTemplate.keys(key);
        if (ObjectUtils.isEmpty(keys)){
            return false;
        }
        try{
            Long ret = instance.stringRedisTemplate.delete(keys);
            return ret > 0;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 取得key分页的数据
     * @param key list 的键
     * @param start 开始下标
     * @param end 结束下标
     * @return
     */
    public static List<String> lrange(String key,int start,int end){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        return instance.stringRedisTemplate.opsForList().range(key,start,end);
    }

    /**
     * list取key的下值的数量
     * @param key
     * @return
     */
    public static long llen(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return 0;
        }
        Long size = instance.stringRedisTemplate.opsForList().size(key);
        return null == size ? 0 : size;
    }

    /**
     * 向key递增1
     * @param key
     * @return
     */
    public static Long incr(String key){
        return incr(key,1,null);
    }

    /**
     * 向key递增1 ,设置expire失效时间
     * @param key
     * @param expired
     * @return
     */
    public static Long incr(String key,Integer expired){
        return incr(key,1,expired);
    }

    /**
     * 向key递增delta数值 并设置expire失效时间
     * @param key
     * @param delta
     * @param expired
     * @return
     */
    public static Long incr(String key, int delta, Integer expired) {
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        Long ret = instance.stringRedisTemplate.opsForValue().increment(key, delta);
        if (null != ret){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
        return ret;
    }

    /**
     * 向key递减1
     * @param key
     * @return
     */
    public static Long decr(String key){
        return incr(key,-1,null);
    }

    /**
     * 向key递减1 ,设置expire失效时间
     * @param key
     * @param expired
     * @return
     */
    public static Long decr(String key,Integer expired){
        return incr(key,-1,expired);
    }

    /**
     * 开始---结尾
     * 向右添加数据 左---→右 添加数据
     * @param key list 键
     * @param value 添加到list的值
     * @param expired list 失效时间
     */
    public static void rpush(String key,String value,Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){
            return ;
        }
        instance.stringRedisTemplate.opsForList().rightPush(key,value);
        if (null != expired){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
    }

    /**
     * 结尾---开始
     * 向左添加数据 右---→左 添加数据
     * @param key list 键
     * @param value 添加到list的值
     * @param expired list 失效时间
     */
    public static void lpush(String key,String value,Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){
            return ;
        }
        instance.stringRedisTemplate.opsForList().leftPush(key,value);
        if (null != expired){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
    }

    /**
     * list中这个key键下删除count个value值
     * @param key list 键
     * @param count 删除多少个值
     * @param value list 中的值
     */
    public static void rmpush(String key,Integer count,String value){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){
            return ;
        }
        instance.stringRedisTemplate.opsForList().remove(key,count,value);
    }

    /**
     * 移出并获取列表的第一个元素(下标为0的元素)
     * @param key list 键
     * @return
     */
    public static String lpop(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        return instance.stringRedisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移出并获取列表的第一个元素(下标为0的元素)
     * @param key list 键
     * @return
     */
    public static void expire(String key,Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return;
        }
        if (null !=  expired){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
    }

    /**
     * 判断key是不存在，设值为1和设置失效时间 成功返回 true
     * 判断key是存在，直接返回false
     * @param key
     * @param expired
     * @return
     */
    public static boolean setnx(String key,String value,Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){
            return false;
        }
        Boolean ret = instance.stringRedisTemplate.opsForValue().setIfAbsent(key, "1");
        if (null == ret || !ret){
            return false;
        }
        if (null != expired){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
        return true;
    }

    /**
     * 判断key是不存在，设值为 value 和设置失效时间 成功返回 true
     * 判断key是存在，直接返回false
     * @param key
     * @param expired
     * @return
     */
    public static boolean setnxv(String key,String value,Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){
            return false;
        }
        Boolean ret = instance.stringRedisTemplate.opsForValue().setIfAbsent(key,value);
        if (null == ret || !ret){
            return false;
        }
        if (null != expired){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
        return true;
    }

    /**
     * 判断键是否存在
     * @param key
     * @return
     */
    public static boolean hasKey(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return false;
        }
        Boolean ret = instance.stringRedisTemplate.hasKey(key);
        return null != ret;
    }

    /**
     * Redis Sadd 命令将一个或多个成员元素加入到集合中，已经存在于集合的成员元素将被忽略。
     * 假如集合 key 不存在，则创建一个只包含添加的元素作成员的集合。
     * 当集合 key 不是集合类型时，返回一个错误。
     * 注意：在 Redis2.4 版本以前， SADD 只接受单个成员值。
     */
    /**
     * 设置set 不重复的值，
     * 向key插入value值 ，
     * expire为键的失效时间
     * @param key
     * @param value
     * @param expired null 无时间
     */
    public static void sadd(String key,String value, Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return;
        }
        Long ret = instance.stringRedisTemplate.opsForSet().add(key, value);
        if (null != expired && null != ret && ret > 0){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
    }

    /**
     * 向key键 的 set 删除一个元素 memeber
     * @param key
     * @param memeber
     */
    public static void srem(String key, String memeber){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return;
        }
        instance.stringRedisTemplate.opsForSet().remove(key,memeber);
    }

    /**
     * set中命令判断成员元素是否是集合的成员。
     * @param key
     * @param value
     * @return
     */
    public static boolean sismember(String key,String value){
        if (StringUtils.isEmpty(value)){
            return false;
        }
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return false;
        }
        Boolean ret = instance.stringRedisTemplate.opsForSet().isMember(key, value);
        return null == ret ? false : ret;
    }

    /**
     * set取出key所有的数据
     * @param key
     * @return
     */
    public static Set<String> smembers(String key){
        key =assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        return instance.stringRedisTemplate.opsForSet().members(key);
    }

    /**
     * set中这个key值的数量
     * @param key
     * @return
     */
    public static Long ssize(String key) {
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        return instance.stringRedisTemplate.opsForSet().size(key);
    }

    // Redis 有序集合(sorted set)
    /**
     Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
     不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
     有序集合的成员是唯一的,但分数(score)却可以重复。
     集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
     */

    /**
     * Redis Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中。
     * 如果某个成员已经是有序集的成员，那么更新这个成员的分数值，并通过重新插入这个成员元素，来保证该成员在正确的位置上。
     * 分数值可以是整数值或双精度浮点数。
     * 如果有序集合 key 不存在，则创建一个空的有序集并执行 ZADD 操作。
     * 当 key 存在但不是有序集类型时，返回一个错误。
     * 注意： 在 Redis 2.4 版本以前， ZADD 每次只能添加一个元素。
     */

    /**
     * @param key 有序集合 key
     * @param member 成员元素
     * @param score 成员的分数值
     * @return 成功与否
     */
    public static boolean zadd(String key,String member,double score,Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return false;
        }
        Boolean ret = instance.stringRedisTemplate.opsForZSet().add(key, member, score);
        if (null == ret || !ret){
            return false;
        }
        if (null != expired){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
        return true;
    }

    /**
     * Redis Zinterstore 命令计算给定的一个或多个有序集的交集，其中给定 key 的数量必须以 numkeys 参数指定，并将该交集(结果集)储存到 destination 。
     * 默认情况下，结果集中某个成员的分数值是所有给定集下该成员分数值之和
     */
    public static long zinterstore(String key, List<String> otherKeys, String destKey, Integer expired) {
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return 0;
        }
        destKey = assembleCacheKey(destKey);
        if (StringUtils.isEmpty(destKey)){
            return 0;
        }
        if (CollectionUtils.isEmpty(otherKeys)){
            return 0;
        }
        List<String> finalOtherKeys  = new ArrayList<>();
        for (String eachOtherKey  : otherKeys){
            finalOtherKeys.add(assembleCacheKey(eachOtherKey));
        }
        Long ret = instance.stringRedisTemplate.opsForZSet().intersectAndStore(key, finalOtherKeys, destKey);
        if (null == ret || ret <= 0){
            return 0;
        }
        if (null != expired){
            instance.stringRedisTemplate.expire(destKey,expired,TimeUnit.SECONDS);
        }
        return ret;
    }

    /**
     * 获取有序集合的第一个元素，通过索引区间来实现，而不是通过分数
     * @param key
     * @return
     */
    public static String zfirst(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        Set<String> stringSet = instance.stringRedisTemplate.opsForZSet().range(key, 0, 0);
        return CollectionUtils.isEmpty(stringSet) ? null : stringSet.toArray()[0].toString();
    }

    /**
     * 删除zset中指定的member值
     * @param key
     * @param member
     * @return
     */
    public static boolean zrem(String key,String member){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return false;
        }
        Long ret = instance.stringRedisTemplate.opsForZSet().remove(key, member);
        return null != ret && ret > 0;
    }

    /**
     * zset取出key中所有的数据
     * @param key
     * @return
     */
    public static Map<String, Double> zscan(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        try {
            Cursor<ZSetOperations.TypedTuple<String>> tupleCursor = instance.stringRedisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
            Map<String, Double> result = new HashMap<>();
            while (tupleCursor.hasNext()){
                ZSetOperations.TypedTuple<String> tuple = tupleCursor.next();
                result.put(tuple.getValue(),tuple.getScore());
            }
            tupleCursor.close();
            return result;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 对key的zset中member进行递增或者递减incrScore值
     * @param key zset 键
     * @param member 目标
     * @param incrScore 要改变的数值
     * @param expired 失效时间
     * @return
     */
    public static Double zincrby(String key, String member, double incrScore, Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)){
            return null;
        }
        Double ret = instance.stringRedisTemplate.opsForZSet().incrementScore(key, member, incrScore);
        if (null != expired && expired > 0){
            instance.stringRedisTemplate.expire(key,expired,TimeUnit.SECONDS);
        }
        return ret;
    }

    /**
     * 找到对key的zset中member值（也就是这个member分数）
     * @param key
     * @param member
     * @return
     */
    public static Double zscore(String key, String member){
        if (StringUtils.isEmpty(member)) {
            return null;
        }
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        return instance.stringRedisTemplate.opsForZSet().score(key,member);
    }

    /**
     * 添加 hash对象
     * hash一般存储对象 key 标记那个用户
     * hashKey用户下的什么信息
     * value 具体数据
     * @param key hash 的键
     * @param hashKey 目标键
     * @param value 存储的值
     * @param expired 失效时间
     */
    public static void hput(String key, String hashKey, Object value, Integer expired){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)) {
            return;
        }
        instance.stringRedisTemplate.opsForHash().put(key,hashKey,value);
        if (null != expired && expired > 0) {
            instance.stringRedisTemplate.expire(key, expired, TimeUnit.SECONDS);
        }
    }

    /**
     * 删除 hash对象 key键 中的 hashKey
     * @param key
     * @param hashKey
     */
    public static void hdel(String key, String hashKey){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)) {
            return;
        }
        instance.stringRedisTemplate.opsForHash().delete(key,hashKey);
    }

    /**
     * 获取 hash对象 key键 中的 hashKey具体的数据
     * @param key
     * @param hashKey
     */
    public static Object hget(String key, String hashKey){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        return instance.stringRedisTemplate.opsForHash().get(key,hashKey);
    }

    /**
     * 获取 hash对象 key键 下有多少个对象数量
     * @param key
     * @return
     */
    public static Long hsize(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        return instance.stringRedisTemplate.opsForHash().size(key);
    }

    /**
     *获 取 hash对象 key键 下有所有的对象
     * @param key
     * @return
     */
    public static List<Object> hgetAll(String key){
        key = assembleCacheKey(key);
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        return instance.stringRedisTemplate.opsForHash().values(key);
    }

    /**
     * 管道批量插入
     * @param saveList
     * @param unit
     * @param timeout
     * @return void
     */
    public static void batchInsert(List<Map<String, String>> saveList, TimeUnit unit, int timeout){
        /* 插入多条数据 */
        instance.stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                for (Map<String,String> needSave : saveList){
                    instance.stringRedisTemplate.opsForValue().set(needSave.get("key"),needSave.get("value"), timeout,unit);
                }
                return null;
            }
        });
    }

    /**
     * batchGet管道形式（推荐，性能高，类型多）
     * @param keyList
     * @return
     */
    public static List<String> batchGet(List<String> keyList){
        /* 批量获取多条数据 */
        List<Object> objects = instance.stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection stringRedisConnection = (StringRedisConnection) redisConnection;
                for (String key : keyList) {
                    stringRedisConnection.get(key);
                }
                return null;
            }
        });
        return objects.stream().map(String::valueOf).collect(Collectors.toList());
    }

    /**
     * 获取单个缓存
     * @param key
     * @param timeOut
     * @param clazz
     * @param fn
     * @param <T>
     * @return
     */
    public static <T>T getByCache(String key,long timeOut,Class<T> clazz,EntityCache<T> fn){
        T obj;
        String val = RedisUtil.get(key);
        if (val == null || "".equals(val)){
            obj = fn.getCahce();
            if (obj != null && obj != ""){
                RedisUtil.set(key, JSONUtil.toJsonStr(obj),timeOut);
            }else {
                RedisUtil.set(key, null,timeOut);
            }
        }else {
            obj = JSONUtil.toBean(val,clazz);
        }
        return obj;
    }

    /**
     * 获取list集合缓存
     * @param key
     * @param timeOut
     * @param clazz
     * @param fn
     * @param <T>
     * @return
     */
    public static <T>List<T> getByCache(String key,Long timeOut,Class<T> clazz,ListCache<T> fn){
        List<T> data;
        String val = RedisUtil.get(key);
        if (val == null || "".equals(val)){
            data = fn.getCache();
            if (data != null && data.size() > 0){
                RedisUtil.set(key, JSONUtil.toJsonStr(data),timeOut);
            }else {
                RedisUtil.set(key, null,timeOut);
            }
        }else {
            data = JSONUtil.toList(val,clazz);
        }
        return data;
    }

    // 单个缓存
    @FunctionalInterface
    public interface EntityCache<T>{
        T getCahce();
    }

    // list集合缓存
    @FunctionalInterface
    public interface ListCache<T>{
        List<T> getCache();
    }
}
